﻿using MongoDB.Driver;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ClientDW.DataAccessLayer.ESGRatings;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.DataLayer;
using Sustainalytics.DataLayer.EsgRatings;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Entities;
using Sustainalytics.Entities.ESGRatings;
using Sustainalytics.ESG.Entities;
using Sustainalytics.EsgRatings.BusinessLogic;
using Sustainalytics.ESGRatings.BusinessLogic.ESGProfile;
using Sustainalytics.ESGRatings.BusinessLogic.Validators;
using Sustainalytics.ESGRatings.DataLayer;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Synchronization.Interface;
using Sustainalytics.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CompanyWeights = Sustainalytics.ESG.Entities.CompanyWeights;
using ReferenceUniverse = Sustainalytics.Entities.ESGRatings.ReferenceUniverse;
using RPESGResearchState = Sustainalytics.ESG.Entities.RPESGResearchState;
using WeightMatrix = Sustainalytics.Entities.ESGRatings.WeightMatrix;

namespace Sustainalytics.ESGRatings.BusinessLogic
{
    // does:
    // - assures ESGProfile  default is populated
    // - validates data provisioning for all active accounts, allactive  weight matrices, all active esg profiles
    // - saves data in the database

    public class ESGRatingsPreparationSyncStep : ISyncStep
    {
        public SyncStepOrder ExecutionOrder => SyncStepOrder.ESGRatingsDataValidation;

        public SyncContext Start(SyncContext syncContext)
        {
            try
            {
                var clientPlatformConnectionString = syncContext.GAConnectionStrings["ClientPlatform"];
                var clientDWConnectionString = syncContext.GAConnectionStrings["ClientDW"];
                var rpHistoryConnectionString = syncContext.GAConnectionStrings["RPHistoricalDataSet"];

                var esgInputFileRepository = EsgInputFileRepository.GetInstance(
                    new FileRepository(clientPlatformConnectionString),
                    new RepositoryCollection<EsgInputFile>(clientPlatformConnectionString));

                syncContext.Log("IEsgInputFileRepository acquired based on connection string " + clientPlatformConnectionString);

                var weightMatricesRepository = new RepositoryCollection<WeightMatrix>(clientDWConnectionString);
                syncContext.Log("IWeightMatricesRepository acquired based on connection string " + clientDWConnectionString);

                var weightMatricesManagement = WeightMatricesManagement.GetInstance(
                         esgInputFileRepository
                         , WeightMatricesRepository.GetInstance(clientDWConnectionString)
                         , new RepositoryCollection<Entities.ESGRatings.ESGProfile>(clientPlatformConnectionString)
                         , new RepositoryCollection<Account>(clientPlatformConnectionString));

                var tempDataSetBuilder = new TemporaryDataSetBuilder();

                var tempDSManagement = new TemporaryDataSetBusinessLogic(
                       MongoFactory.GetCollectionFromDatabase<DSCorporateData>(rpHistoryConnectionString),
                       MongoFactory.GetCollectionFromDatabase<DSIndicatorTemplate>(rpHistoryConnectionString),
                       MongoFactory.GetCollectionFromDatabase<DSCompanyRawScores>(rpHistoryConnectionString),
                       MongoFactory.GetCollectionFromDatabase<DSWeightMatrix>(rpHistoryConnectionString),
                       MongoFactory.GetCollectionFromDatabase<DSReferenceUniverse>(rpHistoryConnectionString),
                       MongoFactory.GetCollectionFromDatabase<HistoricalDataSet>(rpHistoryConnectionString),
                       new TemporaryDataSetBuilder());

                var researchStateRepository = MongoFactory.GetCollectionFromDatabase<RPESGResearchState>(clientDWConnectionString);
                var corporateDataManagement = new CorporateDataManagement(
                    MongoFactory.GetCollectionFromDatabase<CorporateData>(clientDWConnectionString),
                    MongoFactory.GetCollectionFromDatabase<RPESGResearchState>(clientDWConnectionString));

                var defaultReferenceUniverseProcessor = new DefaultReferenceUniverseProcessor(
                    MongoFactory.GetCollectionFromDatabase<ReferenceUniverse>(clientDWConnectionString),
                    MongoFactory.GetCollectionFromDatabase<CorporateData>(clientDWConnectionString));

                var esgDefaultProfileManagement = new ESGProfileManagement(
                    new RepositoryCollection<Entities.ESGRatings.ESGProfile>(clientPlatformConnectionString),
                    new RepositoryCollection<ReferenceUniverse>(clientDWConnectionString),
                    weightMatricesRepository);

                var defaultProfile = esgDefaultProfileManagement.AssureEsgProfile();

                if (defaultProfile == null)
                {
                    throw new NullReferenceException("can't find default profile");
                }

                syncContext.Log("Default ESGProfile acquired");
                syncContext.Log("Beginning updating the esg profiles on the accounts");
                esgDefaultProfileManagement.SetAccountRepository(AccountsRepository.GetAccountsRepository(clientPlatformConnectionString));
                var results = esgDefaultProfileManagement.UpdateEsgProfiles(true);
                results.ToList().ForEach(x => syncContext.Log(x));
                syncContext.Log("Updating the esg profiles was successful");

                syncContext.Log("begin sync raw scores");
                var temporaryDataSetId = Guid.NewGuid();
                syncContext.Log($"Building a new historical dataset marked with id {temporaryDataSetId}");

                Guid latestSetId;
                using (var esgClient = new ESGClient.ESGClient(syncContext.GAConnectionStrings["ComputationServiceAddress"]))
                {
                    latestSetId = esgClient.GetLatestActiveSet().Result;
                }

                syncContext.AddChange(new ChangedElement()
                {
                    AffectedObjectName = ChangedElement.LAST_KNOWN_VALID_SET_ID,
                    AffectedDocumentId = latestSetId,
                    Detail = new Dictionary<string, object>() { { ChangedElement.LAST_KNOWN_VALID_SET_ID, latestSetId } }
                });

                Func<bool> deleteDs = () => tempDSManagement.DeleteSet(temporaryDataSetId).Result;
                syncContext.AddChange(new ChangedElement()
                {
                    AffectedObjectName = ChangedElement.DATA_SET_DELETE_SYNC_FAIL,
                    AffectedDocumentId = temporaryDataSetId,
                    Detail = new Dictionary<string, object>()
                    {
                        {ChangedElement.DATA_SET_DELETE_SYNC_FAIL,deleteDs}
                    }
                });

                var weightMatricesAccountAssociation = new WeightMatrixLoader(
                    weightMatricesManagement,
                    esgDefaultProfileManagement,
                    MongoFactory.GetCollectionFromDatabase<Account>(clientPlatformConnectionString)).GetWeightMatrixAccountAssociation(defaultProfile);

                syncContext.Log("Weight matrices validator acquired");
                var rawScoresProcessor = new ESGRawScoresWeightsValidator(
                    new CorporateDataLoader(MongoFactory.GetCollectionFromDatabase<CorporateData>(clientDWConnectionString)),
                    MongoFactory.GetCollectionFromDatabase<CompanyWeights>(clientDWConnectionString),
                    MongoFactory.GetCollectionFromDatabase<ESG.Entities.RPCompanyESGComputationResult>(clientDWConnectionString),
                    new CompanyWeightsValidator(),
                    syncContext.Log);

                var companiesResearchState = rawScoresProcessor.ProcessAllSecurablesAsync(weightMatricesAccountAssociation, tempDataSetBuilder).Result;

                syncContext.Log("Ended Weight matrices validation");

                syncContext.Log("Changing research flag from matrix validation");
                researchStateRepository.DeleteMany(_ => true);
                researchStateRepository.InsertMany(companiesResearchState);
                syncContext.Log("UpdateHasEsgResearchState - Done");

                syncContext.Log("Updating default reference universe");

                defaultReferenceUniverseProcessor.UpdateReferenceUniverseContent(defaultProfile.ReferenceUniverseId).Wait();

                syncContext.Log("Finished updating default reference universe");

                AddReferenceUniverses(MongoFactory.GetCollectionFromDatabase<ReferenceUniverse>(clientDWConnectionString), corporateDataManagement, tempDataSetBuilder).Wait();

                AddIndicatorTemplates(MongoFactory.GetCollectionFromDatabase<IndicatorTemplate>(clientDWConnectionString), tempDataSetBuilder).Wait();

                AddCorporateDataWithComprehensiveResearch(MongoFactory.GetCollectionFromDatabase<CorporateData>(clientDWConnectionString), tempDataSetBuilder).Wait();

                tempDataSetBuilder.SetDefaultWeightMatrix(defaultProfile.MatrixId);

                var dataset = tempDataSetBuilder.BuildDataSet(temporaryDataSetId, DateTime.UtcNow);

                tempDSManagement.Save(dataset).Wait();

                syncContext.Log($"Temporary data set with id {dataset.Id} was saved in the database.");

                syncContext.Token.ThrowIfCancellationRequested();

                syncContext.Token.ThrowIfCancellationRequested();
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                syncContext.Log(ex.ToString());
                syncContext.Status = SyncOperationStatus.Failed;
                throw;
            }

            return syncContext;
        }

        private async Task AddIndicatorTemplates(IMongoCollection<IndicatorTemplate> indicatorTemplateCollection, TemporaryDataSetBuilder tempDataSetBuilder)
        {
            var indicatorTemplates = await GetIndicatorTemplates(indicatorTemplateCollection, new IndicatorTemplateMapper()).ConfigureAwait(false);

            tempDataSetBuilder.AddIndicatorTemplates(indicatorTemplates);
        }

        private async Task AddReferenceUniverses(
            IMongoCollection<ReferenceUniverse> referenceUniverseCollection,
            CorporateDataManagement corporateDataManagement,
            TemporaryDataSetBuilder tempDataSetBuilder)
        {
            var coverageCompanies = await GetResearchCompaniesAndCoverageCompanies(corporateDataManagement).ConfigureAwait(false);

            var universes = await ValidateDefaultReferenceUniverses(coverageCompanies, referenceUniverseCollection).ConfigureAwait(false);

            tempDataSetBuilder.AddReferenceUniverses(universes);
        }

        public async Task<List<Computing.Entities.IndicatorTemplate>> GetIndicatorTemplates(IMongoCollection<IndicatorTemplate> templateCollection, IndicatorTemplateMapper mapper)
        {
            ConcurrentBag<Computing.Entities.IndicatorTemplate> indicatorList = new ConcurrentBag<Computing.Entities.IndicatorTemplate>();
            await templateCollection.Find(_ => true).ForEachAsync((template) =>
            {
                Computing.Entities.IndicatorTemplate indicatorTemplate;

                if (mapper.Convert(template, out indicatorTemplate))
                {
                    indicatorList.Add(indicatorTemplate);
                }
            }).ConfigureAwait(false);

            return indicatorList.ToList();
        }

        public async Task<List<Computing.Entities.ESG.ReferenceUniverse>> ValidateDefaultReferenceUniverses(
            Dictionary<Guid, ReferenceUniverseCompany> coverageCompanies,
            IMongoCollection<ReferenceUniverse> referenceUniverseCollection)
        {
            var universes = await referenceUniverseCollection.Find(_ => true).ToListAsync();

            var universesData = new List<Computing.Entities.ESG.ReferenceUniverse>();

            foreach (var universe in universes)
            {
                var companiesList = new List<Computing.Entities.ESG.ReferenceUniverseCompany>();

                foreach (var company in universe.Companies)
                {
                    var companyInfo = company;
                    if (coverageCompanies.ContainsKey(company.CompanyId))
                    {
                        companyInfo = coverageCompanies[company.CompanyId];
                    }

                    companiesList.Add(new Computing.Entities.ESG.ReferenceUniverseCompany(companyInfo.CompanyId, companyInfo.CapitalIqId.ToString(), companyInfo.CompanyName));
                }

                universesData.Add(new Computing.Entities.ESG.ReferenceUniverse { Id = universe.Id, Name = universe.Name, Companies = companiesList.Distinct().ToArray() });
            }

            return universesData;
        }

        public async Task<Dictionary<Guid, ReferenceUniverseCompany>> GetResearchCompaniesAndCoverageCompanies(CorporateDataManagement corporateDataManagement)
        {
            Dictionary<Guid, ReferenceUniverseCompany> result = new Dictionary<Guid, ReferenceUniverseCompany>();
            var coverageCompanies = await corporateDataManagement.GetCoverageCompanies();
            var researchCompanyIds = coverageCompanies.Select(x => x.ResearchParentId.Value);
            var researchCompaniesForCoverage = await corporateDataManagement.GetCompaniesWithEsgResearchAsync(
                null,
                null,
                researchCompanyIds);

            foreach (var research in researchCompaniesForCoverage)
            {
                var coverage = coverageCompanies.FirstOrDefault(c => c.ResearchParentId.HasValue && c.ResearchParentId.Value == research.Id);
                result.Add(coverage.Id, new ReferenceUniverseCompany(research.Id, research.CapitalIqId, research.CompanyName));
            }
            return result;
        }

        private async Task AddCorporateDataWithComprehensiveResearch(IMongoCollection<CorporateData> corporatedataCollection, TemporaryDataSetBuilder builder)
        {
            var result = await corporatedataCollection.Find(x => x.ComprehensiveEsgResearchState.ResearchState == ResearchState.ResearchAvailable && x.ResearchParentId == null)
                .Project(x => new Computing.Entities.ESG.CompanyData
                {
                    Id = x.Id,
                    TemplateType = x.TemplateType,
                    CompanyType = x.CompanyType,
                    IndustryGroup = x.SustainalyticsIndustryGroup,
                    PeerGroup = x.PeerGroup
                }).ToListAsync().ConfigureAwait(false);
            builder.AddCompanies(result);
        }
    }

    internal class WeightMatrixLoader
    {
        private readonly IWeightMatricesManagement _weightMatricesManagement;
        private readonly ESGProfileManagement _esgDefaultProfileManagement;
        private readonly IMongoCollection<Account> _accountCollection;

        public WeightMatrixLoader(IWeightMatricesManagement weightMatricesManagement, ESGProfileManagement esgDefaultProfileManagement, IMongoCollection<Account> accountCollection)
        {
            _weightMatricesManagement = weightMatricesManagement;
            _esgDefaultProfileManagement = esgDefaultProfileManagement;
            _accountCollection = accountCollection;
        }

        public Dictionary<Guid, AccountEsgProfileMatrixAssociations> GetWeightMatrixAccountAssociation(Entities.ESGRatings.ESGProfile defaultProfile)

        {
            var associations = new Dictionary<Guid, AccountEsgProfileMatrixAssociations>();

            var activeWeightMatrices = _weightMatricesManagement.GetActiveWeightMatrices().ToList();

            var activeAccounts = _accountCollection.Find(a => a.Status == Status.Active).ToListAsync().Result;

            var activeEsgProfilesIds = activeAccounts.Select(a => a.CurrentEsgProfile);

            var esgProfiles = _esgDefaultProfileManagement.LoadESGProfiles(activeEsgProfilesIds);

            if (!esgProfiles.Exists(x => x.Id == defaultProfile.Id))
            {
                esgProfiles.Add(defaultProfile);
            }

            if (activeWeightMatrices.IsNullOrEmpty())
            {
                throw new NullReferenceException("activeWeightMatrices");
            }

            foreach (var matrix in activeWeightMatrices)
            {
                var matrixId = matrix.Id;
                var accountId = matrix.AccountId;
                var associatedEsgProfiles = esgProfiles.SingleOrDefault(x => x.MatrixId == matrixId && x.AccountId == accountId);
                associations.Add(
                    matrixId,
                    associatedEsgProfiles == null
                        ? new AccountEsgProfileMatrixAssociations(null, accountId, matrixId, matrix.Name)
                        : new AccountEsgProfileMatrixAssociations(associatedEsgProfiles.Id, accountId, matrixId, matrix.Name));
            }

            if (!associations.ContainsKey(defaultProfile.MatrixId))
                associations.Add(defaultProfile.MatrixId, new AccountEsgProfileMatrixAssociations(defaultProfile.Id, null, defaultProfile.MatrixId, "Default"));

            return associations;
        }
    }

    public class AccountEsgProfileMatrixAssociations
    {
        public AccountEsgProfileMatrixAssociations(Guid? esgProfileId, Guid? accountId, Guid matrixId, string matrixName)
        {
            MatrixName = matrixName;
            MatrixId = matrixId;
            EsgProfileId = esgProfileId;
            AccountId = accountId;
        }

        public Guid? AccountId { get; private set; }

        public Guid? EsgProfileId { get; private set; }

        public Guid MatrixId { get; private set; }
        public string MatrixName { get; private set; }
    }
}
